package client

import (
	"context"
	"fmt"
	"gitee.com/aqchain/backend/config"
	"gitee.com/aqchain/backend/models"
	"gitee.com/aqchain/go-ethereum"
	"gitee.com/aqchain/go-ethereum/accounts/abi/bind"
	"gitee.com/aqchain/go-ethereum/accounts/keystore"
	"gitee.com/aqchain/go-ethereum/common"
	fileStore "gitee.com/aqchain/go-ethereum/contracts/fileStore/contract"
	"gitee.com/aqchain/go-ethereum/core/types"
	"gitee.com/aqchain/go-ethereum/ethclient"
	"gitee.com/aqchain/go-ethereum/rpc"
	"github.com/fogleman/gg"
	"gorm.io/gorm"
	"io/ioutil"
	"log"
	"math/big"
	"os"
	"path/filepath"
	"strconv"
	"time"
)

// Client 用于连接区块链和获取区块链数据
type Client struct {
	ethClient *ethclient.Client     // 以太坊RPC连接
	sub       ethereum.Subscription // 以太坊RPC连接
	fileStore *fileStore.FileStore  // 智能合约
	key       *keystore.Key         // 本地私钥
	db        *gorm.DB              // 本地数据库
	config    *config.Config        // 配置文件

	isSub bool
}

func New(ctx context.Context, cfg *config.Config, db *gorm.DB) (*Client, error) {
	// 连接区块链节点
	client, err := rpc.Dial(cfg.RPCAddress)
	if err != nil {
		return nil, err
	}
	// 读取本地节点密钥
	keyBytes, err := ioutil.ReadFile(cfg.Key)
	if err != nil {
		return nil, err
	}
	key, err := keystore.DecryptKey(keyBytes, cfg.Auth)

	// 创建合约实例
	ethClient := ethclient.NewClient(client)
	instance, err := fileStore.NewFileStore(common.HexToAddress(cfg.Contract), ethClient)
	c := &Client{ethClient: ethClient, fileStore: instance, key: key, db: db, config: cfg}

	// 开启数据同步
	go c.subscribe(ctx)
	//go c.sync(ctx)

	return c, nil
}

// sync 同步区块链数据
//func (c *Client) sync(ctx context.Context) {
//	var index int64
//	// todo 保存一个已经同步位置 用于下次启动后读取
//	index = 1
//	for {
//		index++
//		block, err := c.ethClient.BlockByNumber(ctx, big.NewInt(index))
//		if err != nil {
//			log.Println(err)
//			break
//		}
//		// 保存区块
//		blockStorage:= models.Block{
//			Number:block.NumberU64(),
//			Time:     block.Time().Uint64(),
//			Hash:     block.Hash().Hex(),
//			TxHash:   block.TxHash().Hex(),
//			Coinbase: block.Coinbase().Hex(),
//			Size: float64(block.Size()),
//			Transactions: []models.Transaction{},
//		}
//
//		for _, tx := range block.Transactions() {
//			// 保存交易
//			txStorage:= models.Transaction{
//				Hash:        tx.Hash().Hex(),
//				BlockNumber: block.NumberU64(),
//				To:          tx.To().Hex(),
//				Size: float64(tx.Size()),
//				Cost: tx.Cost().Uint64(),
//			}
//			blockStorage.Transactions = append(blockStorage.Transactions, txStorage)
//		}
//		err = c.db.FirstOrCreate(&blockStorage).Error
//		if err != nil {
//			log.Println(err)
//		}
//	}
//}

func (c *Client) subscribe(ctx context.Context) {
	// 订阅新区块
	var err error
	headers := make(chan *types.Header)
	c.sub, err = c.ethClient.SubscribeNewHead(ctx, headers)
	if err != nil {
		log.Println(err)
	}

	c.isSub = true
	ticker := time.NewTicker(time.Second * 2)

	go func() {
		for {
			select {
			case err := <-c.sub.Err():
				log.Println(err)
				c.isSub = false
			case <-ticker.C:
				if !c.isSub {
					sub, err := c.ethClient.SubscribeNewHead(ctx, headers)
					if err != nil {
						log.Println(err)
					} else {
						c.sub = sub
						c.isSub = true
					}
				}
			case header := <-headers:
				// 同步接收到的数据到数据库 没有用事务
				block, err := c.ethClient.BlockByHash(ctx, header.Hash())
				if err != nil {
					log.Println(err)
				}

				// 保存区块信息
				blockStorage := models.Block{
					Number:       block.NumberU64(),
					Time:         block.Time().Uint64(),
					Hash:         block.Hash().Hex(),
					TxHash:       block.TxHash().Hex(),
					Coinbase:     block.Coinbase().Hex(),
					Size:         float64(block.Size()),
					Transactions: []models.Transaction{},
				}

				for _, tx := range block.Transactions() {
					// 保存交易信息
					txStorage := models.Transaction{
						Hash:        tx.Hash().Hex(),
						BlockNumber: block.NumberU64(),
						Size:        float64(tx.Size()),
						Cost:        tx.Cost().Uint64(),
					}
					if tx.To() != nil {
						txStorage.To = tx.To().Hex()
					}
					blockStorage.Transactions = append(blockStorage.Transactions, txStorage)

					// 检查调用文件上传的合约
					if tx.To() != nil && tx.To().Hex() == common.HexToAddress(c.config.Contract).Hex() {

						txHash := tx.Hash().Hex()
						trade := models.Trade{}
						err = c.db.Where(&models.Trade{TxHash: txHash}).Preload("From").First(&trade).Error
						if err != nil {
							log.Println(err)
						}
						trade.BlockNumber = block.NumberU64()
						trade.BlockTime = block.Time().Uint64()
						err = c.db.Model(&trade).
							Updates(
								&models.Trade{
									BlockTime:   block.Time().Uint64(),
									BlockNumber: block.NumberU64(),
								}).Error
						if err != nil {
							log.Println(err)
						}

						item := models.Item{}
						err = c.db.Where(&models.Item{TxHash: txHash}).First(&item).Error
						if err != nil {
							log.Println(err)
						}
						err = c.generateCertificate(item, trade)
						if err != nil {
							log.Println(err)
						}

						err = c.db.Model(&item).Updates(&models.Item{Status: 1}).Error
						if err != nil {
							log.Println(err)
						}

					}
				}

				err = c.db.Create(&blockStorage).Error
				if err != nil {
					log.Println(err)
				}
			}
		}
	}()
}

func (c *Client) generateCertificate(item models.Item, trade models.Trade) error {
	wd, _ := os.Getwd()
	im, err := gg.LoadImage(wd + "/test/certificate.png")
	if err != nil {
		return err
	}
	w := im.Bounds().Size().X
	h := im.Bounds().Size().Y

	dc := gg.NewContext(w, h)
	dc.DrawImage(im, 0, 0)
	dc.SetRGB(0, 0, 0)
	if err := dc.LoadFontFace(wd+"/test/方正粗黑宋简体.ttf", 50); err != nil {
		return err
	}
	dc.DrawStringAnchored("数字内容存证证明", float64(w/2), 200, 0.5, 0.5)

	if err := dc.LoadFontFace(wd+"/test/方正粗黑宋简体.ttf", 15); err != nil {
		return err
	}

	boldText := []string{
		//fmt.Sprintf("编号：BC%s",item.Hash),
		"存证用户",
		"文件哈希值",
		"区块链交易编号",
		"数据名称",
		"数据大小",
	}
	//dc.DrawStringAnchored(boldText[0], float64(w/2), 250, 0.5, 0.5)
	for i := 0; i < len(boldText); i++ {
		dc.DrawStringAnchored(boldText[i], 130, float64(400+50*i), 0.5, 0.5)
	}

	if err := dc.LoadFontFace(wd+"/test/方正粗黑宋简体.ttf", 15); err != nil {
		return err
	}
	normalText := []string{
		item.Creator.Username,
		item.FileHash,
		item.TxHash,
		item.FileName,
		strconv.FormatInt(item.FileSize, 10),
	}

	for i := 0; i < len(boldText); i++ {
		dc.DrawStringAnchored(normalText[i], 480, float64(400+50*i), 0.5, 0.5)
	}

	text := fmt.Sprintf("%s接收到安权链区块存证信息，该存证事件包括以下信息，特此证明", time.Unix(int64(trade.BlockTime), 0).Format("2006年01月02日15时04分05秒"))
	dc.DrawStringWrapped(text, 200, 320, 0.5, 0.5, 200, 1.5, gg.AlignLeft)

	dir := filepath.Join(wd, c.config.TempDir, config.CertificationDir)
	path := fmt.Sprintf("%s.png", filepath.Join(dir, item.FileHash))
	err = dc.SavePNG(path)
	if err != nil {
		return err
	}

	return nil
}

func (c *Client) GetAddress() common.Address {
	return c.key.Address
}

func (c *Client) GetOwner(fileHash string) (common.Address, error) {
	return c.fileStore.OwnerOf(&bind.CallOpts{From: c.key.Address, Context: context.Background()}, fileHash)
}

func (c *Client) GetNonce(ctx context.Context, address common.Address) (uint64, error) {
	nonce, err := c.ethClient.PendingNonceAt(ctx, address)
	if err != nil {
		return 0, err
	}
	return nonce, nil
}

func (c *Client) SetOwner(ctx context.Context, fileHash string, owner common.Address) (string, error) {
	transOpts := bind.NewKeyedTransactor(c.key.PrivateKey)
	transOpts.Context = ctx
	transOpts.GasLimit = 500000
	tx, err := c.fileStore.SetOwner(transOpts, fileHash, owner)

	if err != nil {
		return "", err
	}
	return tx.Hash().Hex(), nil
}

func (c *Client) Transfer(ctx context.Context, fileHash string, from common.Address, to common.Address, price *big.Int) (string, error) {
	transOpts := bind.NewKeyedTransactor(c.key.PrivateKey)
	transOpts.Context = ctx
	transOpts.GasLimit = 500000
	tx, err := c.fileStore.Transfer(transOpts, from, to, fileHash, price)

	if err != nil {
		return "", err
	}
	return tx.Hash().Hex(), nil
}

func (c *Client) SetLicense(ctx context.Context, fileHash string, from common.Address, to common.Address, price *big.Int) (string, error) {
	transOpts := bind.NewKeyedTransactor(c.key.PrivateKey)
	transOpts.Context = ctx
	transOpts.GasLimit = 500000
	tx, err := c.fileStore.License(transOpts, from, to, fileHash, price)

	if err != nil {
		return "", err
	}
	return tx.Hash().Hex(), nil
}
